#ifndef WALLE_COMMON_COMMAD_LINE_H_
#define WALLE_COMMON_COMMAD_LINE_H_
#include <walle/algo/lexical_cast.h>
#include <map>
#include <cxxabi.h>
#include <vector>
 
namespace walle {
namespace detail {

static inline std::string demangle(const std::string &name)
{
  int status=0;
  char *p=abi::__cxa_demangle(name.c_str(), 0, 0, &status);
  std::string ret(p);
  free(p);
  return ret;
}

template <class T>
std::string readable_typename()
{
  return demangle(typeid(T).name());
}

template <>
inline std::string readable_typename<std::string>()
{
  return "string";
}
}

class CmdParser{
private:
	class option_base;
public:
	CmdParser(){}
	~CmdParser()
	{
		std::map<std::string, option_base*>::iterator p = options.begin();
		 for (;p!=options.end(); p++)
      		delete p->second;
	}

	bool add(const std::string &name,
           char short_name=0,
           const std::string &desc="")
	{
	    if (options.count(name)) {
	    	  return false;
	    }
	    options[name]=new option_without_value(name, short_name, desc);
	    ordered.push_back(options[name]);
	    return true;
  	}

	template <class T>
  	bool add(const std::string &name,
           char short_name=0,
           const std::string &desc="",
           bool need=true,
           const T def=T())
  	{
    	return add(name, short_name, desc, need, def, default_reader<T>());
  	}

  	template <class T, class F>
  	bool add(const std::string &name,
           char short_name=0,
           const std::string &desc="",
           bool need=true,
           const T def=T(),
           F reader=F())
  	{
	    if (options.count(name)) {
	    	  return false;
	    }
	    options[name]=new option_with_value_with_reader<T, F>(name, short_name, need, def, desc, reader);
	    ordered.push_back(options[name]);
	    return true;
  	}

  	void footer(const std::string &f)
  	{
    	ftr=f;
  	}

	void set_program_name(const std::string &name)
	{
		prog_name=name;
	}

	bool exist(const std::string &name) const 
	{
    	if (options.count(name)==0) {
    		  return false;
    	}
    	return options.find(name)->second->has_set();
  	}

  	template <class T>
  	bool get(const std::string &name, T &ret) const 
  	{
	    if (options.count(name)==0) {
	    	  return false;
	    }
	    const option_with_value<T> *p=dynamic_cast<const option_with_value<T>*>(options.find(name)->second);
	    if (p==NULL){
	    	  return false;
	    }
	    ret = p->get();
	    return true;
  	}

  	const std::vector<std::string> &rest() const 
  	{
    	return others;
  	}

  	bool parse(const std::string &arg)
  	{
	    std::vector<std::string> args;

	    std::string buf;
	    bool in_quote=false;
	    for (std::string::size_type i=0; i<arg.length(); i++){
			if (arg[i]=='\"'){
  				in_quote=!in_quote;
  				continue;
			}

	      	if (arg[i]==' ' && !in_quote){
	        	args.push_back(buf);
	        	buf="";
	        	continue;
	      	}

	      	if (arg[i]=='\\'){
	        	i++;
	        	if (i>=arg.length()){
	          		errors.push_back("unexpected occurrence of '\\' at end of string");
	          		return false;
	        	}
	      	}

	      	buf+=arg[i];
	    }

	    if (in_quote){
	      	errors.push_back("quote is not closed");
	      	return false;
	    }

	    if (buf.length()>0)
	      	args.push_back(buf);

	    for (size_t i=0; i<args.size(); i++)
	      	std::cout<<"\""<<args[i]<<"\""<<std::endl;

	    return parse(args);
  	}

	bool parse(const std::vector<std::string> &args)
	{
	    int argc=static_cast<int>(args.size());
	    std::vector<const char*> argv(argc);

	    for (int i=0; i<argc; i++)
	      argv[i]=args[i].c_str();

	    return parse(argc, &argv[0]);
	}

	bool parse(int argc, const char * const argv[])
	{
    	errors.clear();
    	others.clear();

    	if (argc<1){
      		errors.push_back("argument number must be longer than 0");
      		return false;
    	}
    	
    	if (prog_name=="")
      		prog_name=argv[0];

    	std::map<char, std::string> lookup;
    	for (std::map<std::string, option_base*>::iterator p=options.begin();
        		p!=options.end(); p++){
      		if (p->first.length()==0) {
      			continue;
      		}
      		char initial=p->second->short_name();
      		if (initial){
        		if (lookup.count(initial)>0){
          			lookup[initial]="";
          			errors.push_back(std::string("short option '")+initial+"' is ambiguous");
          			return false;
        		} else {
        			lookup[initial]=p->first;
        		}
      		}
    	}

    	for (int i=1; i<argc; i++){
      		if (strncmp(argv[i], "--", 2)==0){
        		const char *p=strchr(argv[i]+2, '=');
        		if (p){
          			std::string name(argv[i]+2, p);
          			std::string val(p+1);
          			set_option(name, val);
        		} else {
          			std::string name(argv[i]+2);
          			if (options.count(name)==0){
            			errors.push_back("undefined option: --"+name);
            			continue;
          			}
          
          			if (options[name]->has_value()){
            			if (i+1>=argc){
              				errors.push_back("option needs value: --"+name);
              				continue;
            			} else{
              				i++;
              				set_option(name, argv[i]);
            			}
          			} else{
            			set_option(name);
          			}
          		}
        	} else if (strncmp(argv[i], "-", 1)==0){
        		if (!argv[i][1]) {
        			continue;
        		}
        		char last=argv[i][1];
        		for (int j=2; argv[i][j]; j++){
          			last=argv[i][j];
          			if (lookup.count(argv[i][j-1])==0){
            			errors.push_back(std::string("undefined short option: -")+argv[i][j-1]);
            			continue;
          			}
          			if (lookup[argv[i][j-1]]==""){
            			errors.push_back(std::string("ambiguous short option: -")+argv[i][j-1]);
            			continue;
          			}
          			set_option(lookup[argv[i][j-1]]);
        		}

        		if (lookup.count(last)==0){
          			errors.push_back(std::string("undefined short option: -")+last);
          			continue;
        		}
        		if (lookup[last]==""){
          			errors.push_back(std::string("ambiguous short option: -")+last);
          			continue;
        		}

        		if (i+1<argc && options[lookup[last]]->has_value()){
          			set_option(lookup[last], argv[i+1]);
          			i++;
        		} else{
          			set_option(lookup[last]);
        		}
      		} else{
        		others.push_back(argv[i]);
      		}
    	 }

      	for (std::map<std::string, option_base*>::iterator p=options.begin();
           		p!=options.end(); p++)
        		if (!p->second->valid())
          		errors.push_back("need option: --"+std::string(p->first));

      	return errors.size()==0;
  	}

  	void parse_check(const std::string &arg)
  	{
      	if (!options.count("help"))
        		add("help", '?', "print this message");
      	check(0, parse(arg));
  	}

  	void parse_check(const std::vector<std::string> &args)
  	{
      	if (!options.count("help"))
        		add("help", '?', "print this message");
      	check(args.size(), parse(args));
  	}

  	void parse_check(int argc, char *argv[])
  	{
      	if (!options.count("help"))
        		add("help", '?', "print this message");
      	check(argc, parse(argc, argv));
  	}

  	std::string error() const
  	{
    	 return errors.size()>0?errors[0]:"";
  	}

  	std::string error_full() const
  	{
      	std::ostringstream oss;
      	for (size_t i=0; i<errors.size(); i++)
        		oss<<errors[i]<<std::endl;
      	return oss.str();
  	}

  	std::string usage() const 
  	{
      	std::ostringstream oss;
      	oss<<"usage: "<<prog_name<<" ";
      	for (size_t i=0; i<ordered.size(); i++){
        		if (ordered[i]->must())
          	oss<<ordered[i]->short_description()<<" ";
      	}
      
      	oss<<"[options] ... "<<ftr<<std::endl;
      	oss<<"options:"<<std::endl;

      	size_t max_width=0;
      	for (size_t i=0; i<ordered.size(); i++){
        		max_width=std::max(max_width, ordered[i]->name().length());
      	}
      	for (size_t i=0; i<ordered.size(); i++){
        		if (ordered[i]->short_name()){
          		oss<<"  -"<<ordered[i]->short_name()<<", ";
        		} else{
          		oss<<"      ";
        		}

        		oss<<"--"<<ordered[i]->name();
        		for (size_t j=ordered[i]->name().length(); j<max_width+4; j++)
          		oss<<' ';
        		oss<<ordered[i]->description()<<std::endl;
      	}
      	return oss.str();
  	}

private:
	void check(int argc, bool ok)
	{
    	if ((argc==1 && !ok) || exist("help")){
      		std::cerr<<usage();
      		exit(0);
    	}

    	if (!ok){
      		std::cerr<<error()<<std::endl<<usage();
      		exit(1);
    	}
  	}

  	void set_option(const std::string &name)
  	{
      	if (options.count(name)==0){
        		errors.push_back("undefined option: --"+name);
        		return;
      	}
      	if (!options[name]->set()){
        		errors.push_back("option needs value: --"+name);
        		return;
      	}
  	}

  	void set_option(const std::string &name, const std::string &value)
  	{
      	if (options.count(name)==0){
        		errors.push_back("undefined option: --"+name);
        		return;
      	}
      	if (!options[name]->set(value)){
        		errors.push_back("option value is invalid: --"+name+"="+value);
        		return;
      	}
  	}


private:
	std::map<std::string, option_base*> options;
  	std::vector<option_base*> ordered;
  	std::string ftr;

  	std::string prog_name;
  	std::vector<std::string> others;

  	std::vector<std::string> errors;
private:
	class option_base{
  	public:
	    virtual ~option_base(){}

	    virtual bool has_value() const=0;
	    virtual bool set()=0;
	    virtual bool set(const std::string &value)=0;
	    virtual bool has_set() const=0;
	    virtual bool valid() const=0;
	    virtual bool must() const=0;

	    virtual const std::string &name() const=0;
	    virtual char short_name() const=0;
	    virtual const std::string &description() const=0;
	    virtual std::string short_description() const=0;
  };
  class option_without_value : public option_base {
  public:
    option_without_value(const std::string &pname,
                         char shortname,
                         const std::string &desc)
      :_nam(pname), _snam(shortname), _desc(desc), _has(false)
    {
    }
    ~option_without_value(){}

    bool has_value() const { return false; }

    bool set()
    {
        _has=true;
        return true;
    }
    bool set(const std::string &)
    {
        return false;
    }

    bool has_set() const 
    {
        return _has;
    }

    bool valid() const
    {
        return true;
    }

    bool must() const
    {
        return false;
    }

    const std::string &name() const
    {
        return _nam;
    }

    char short_name() const
    {
        return _snam;
    }

    const std::string &description() const 
    {
        return _desc;
    }

    std::string short_description() const
    {
        return "--"+_nam;
    }

  private:
      std::string _nam;
      char        _snam;
      std::string _desc;
      bool        _has;
  };

  template <class T>
  class option_with_value : public option_base {
  public:
    option_with_value(const std::string &longname,
                      char shortname,
                      bool pneed,
                      const T &pdef,
                      const std::string &pdesc)
      : _nam(longname), _snam(shortname), _need(pneed), _has(false)
      , _def(pdef), _actual(pdef) {
      this->_desc=full_description(pdesc);
    }
    ~option_with_value(){}

    const T &get() const 
    {
        return _actual;
    }

    bool has_value() const { return true; }

    bool set()
    {
        return false;
    }

    bool set(const std::string &value)
    {
    		if(read(value, _actual)) {
      			_has = true;
      			return true;
    		}
    		return false;
    }

    bool has_set() const
    {
        return _has;
    }

    bool valid() const
    {
        if (_need && !_has) return false;
        return true;
    }

    bool must() const
    {
        return _need;
    }

    const std::string &name() const
    {
        return _nam;
    }

    char short_name() const
    {
        return _snam;
    }

    const std::string &description() const 
    {
        return _desc;
    }

    std::string short_description() const
    {
        return "--"+_nam+"="+detail::readable_typename<T>();
    }

protected:
    std::string full_description(const std::string &pdesc)
    {
        return pdesc+" ("+detail::readable_typename<T>()+
        (_need?"":" [="+default_value<T>(_def)+"]")
        +")";
    }

    virtual bool read(const std::string &s, T &ret)=0;

    std::string _nam;
    char        _snam;
    bool        _need;
    std::string _desc;

    bool        _has;
    T           _def;
    T           _actual;
  };

  template <class T, class F>
  class option_with_value_with_reader : public option_with_value<T> {
  public:
    option_with_value_with_reader(const std::string &pname,
                                  char shortname,
                                  bool pneed,
                                  const T pdef,
                                  const std::string &pdesc,
                                  F rder)
      : option_with_value<T>(pname, shortname, pneed, pdef, pdesc), reader(rder){
    }

  private:
    bool read(const std::string &s, T &ret)
    {
      	return reader(s, ret);
    }

    F reader;
  };


};
}

#endif